// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/scheduler/begin_frame_source.h"

#include <stddef.h>

#include "base/atomic_sequence_num.h"
#include "base/auto_reset.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "cc/scheduler/delay_based_time_source.h"
#include "cc/scheduler/scheduler.h"

namespace cc {

namespace {
    // kDoubleTickDivisor prevents the SyntheticBFS from sending BeginFrames too
    // often to an observer.
    static const double kDoubleTickDivisor = 2.0;
}

// BeginFrameObserverBase -------------------------------------------------
BeginFrameObserverBase::BeginFrameObserverBase()
    : last_begin_frame_args_()
    , dropped_begin_frame_args_(0)
{
}

const BeginFrameArgs& BeginFrameObserverBase::LastUsedBeginFrameArgs() const
{
    return last_begin_frame_args_;
}

void BeginFrameObserverBase::OnBeginFrame(const BeginFrameArgs& args)
{
    DCHECK(args.IsValid());
    DCHECK(args.frame_time >= last_begin_frame_args_.frame_time);
    DCHECK(args.sequence_number > last_begin_frame_args_.sequence_number || args.source_id != last_begin_frame_args_.source_id);
    bool used = OnBeginFrameDerivedImpl(args);
    if (used) {
        last_begin_frame_args_ = args;
    } else {
        ++dropped_begin_frame_args_;
    }
}

// BeginFrameSource -------------------------------------------------------
namespace {
    static base::StaticAtomicSequenceNumber g_next_source_id;
} // namespace

BeginFrameSource::BeginFrameSource()
    : source_id_(g_next_source_id.GetNext())
{
}

uint32_t BeginFrameSource::source_id() const
{
    return source_id_;
}

// StubBeginFrameSource ---------------------------------------------------
bool StubBeginFrameSource::IsThrottled() const
{
    return true;
}

// SyntheticBeginFrameSource ----------------------------------------------
SyntheticBeginFrameSource::~SyntheticBeginFrameSource() = default;

// BackToBackBeginFrameSource ---------------------------------------------
BackToBackBeginFrameSource::BackToBackBeginFrameSource(
    std::unique_ptr<DelayBasedTimeSource> time_source)
    : time_source_(std::move(time_source))
    , next_sequence_number_(BeginFrameArgs::kStartingFrameNumber)
    , weak_factory_(this)
{
    time_source_->SetClient(this);
    // The time_source_ ticks immediately, so we SetActive(true) for a single
    // tick when we need it, and keep it as SetActive(false) otherwise.
    time_source_->SetTimebaseAndInterval(base::TimeTicks(), base::TimeDelta());
}

BackToBackBeginFrameSource::~BackToBackBeginFrameSource() = default;

void BackToBackBeginFrameSource::AddObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) == observers_.end());
    observers_.insert(obs);
    pending_begin_frame_observers_.insert(obs);
    obs->OnBeginFrameSourcePausedChanged(false);
    time_source_->SetActive(true);
}

void BackToBackBeginFrameSource::RemoveObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) != observers_.end());
    observers_.erase(obs);
    pending_begin_frame_observers_.erase(obs);
    if (pending_begin_frame_observers_.empty())
        time_source_->SetActive(false);
}

void BackToBackBeginFrameSource::DidFinishFrame(BeginFrameObserver* obs,
    const BeginFrameAck& ack)
{
    if (ack.remaining_frames == 0 && observers_.find(obs) != observers_.end()) {
        pending_begin_frame_observers_.insert(obs);
        time_source_->SetActive(true);
    }
}

bool BackToBackBeginFrameSource::IsThrottled() const
{
    return false;
}

void BackToBackBeginFrameSource::OnTimerTick()
{
    base::TimeTicks frame_time = time_source_->LastTickTime();
    base::TimeDelta default_interval = BeginFrameArgs::DefaultInterval();
    BeginFrameArgs args = BeginFrameArgs::Create(
        BEGINFRAME_FROM_HERE, source_id(), next_sequence_number_, frame_time,
        frame_time + default_interval, default_interval, BeginFrameArgs::NORMAL);
    next_sequence_number_++;

    // This must happen after getting the LastTickTime() from the time source.
    time_source_->SetActive(false);

    std::unordered_set<BeginFrameObserver*> pending_observers;
    pending_observers.swap(pending_begin_frame_observers_);
    DCHECK(!pending_observers.empty());
    for (BeginFrameObserver* obs : pending_observers)
        obs->OnBeginFrame(args);
}

// DelayBasedBeginFrameSource ---------------------------------------------
DelayBasedBeginFrameSource::DelayBasedBeginFrameSource(
    std::unique_ptr<DelayBasedTimeSource> time_source)
    : time_source_(std::move(time_source))
    , next_sequence_number_(BeginFrameArgs::kStartingFrameNumber)
{
    time_source_->SetClient(this);
}

DelayBasedBeginFrameSource::~DelayBasedBeginFrameSource() = default;

void DelayBasedBeginFrameSource::OnUpdateVSyncParameters(
    base::TimeTicks timebase,
    base::TimeDelta interval)
{
    if (!authoritative_interval_.is_zero()) {
        interval = authoritative_interval_;
    } else if (interval.is_zero()) {
        // TODO(brianderson): We should not be receiving 0 intervals.
        interval = BeginFrameArgs::DefaultInterval();
    }

    last_timebase_ = timebase;
    time_source_->SetTimebaseAndInterval(timebase, interval);
}

void DelayBasedBeginFrameSource::SetAuthoritativeVSyncInterval(
    base::TimeDelta interval)
{
    authoritative_interval_ = interval;
    OnUpdateVSyncParameters(last_timebase_, interval);
}

BeginFrameArgs DelayBasedBeginFrameSource::CreateBeginFrameArgs(
    base::TimeTicks frame_time,
    BeginFrameArgs::BeginFrameArgsType type)
{
    uint64_t sequence_number = next_sequence_number_++;
    return BeginFrameArgs::Create(
        BEGINFRAME_FROM_HERE, source_id(), sequence_number, frame_time,
        time_source_->NextTickTime(), time_source_->Interval(), type);
}

void DelayBasedBeginFrameSource::AddObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) == observers_.end());

    observers_.insert(obs);
    obs->OnBeginFrameSourcePausedChanged(false);
    time_source_->SetActive(true);

    // Missed args should correspond to |current_begin_frame_args_| (particularly,
    // have the same sequence number) if |current_begin_frame_args_| still
    // correspond to the last time the time source should have ticked. This may
    // not be the case if OnTimerTick() has never run yet, the time source was
    // inactive before AddObserver() was called, or the interval changed. In such
    // a case, we create new args with a new sequence number.
    base::TimeTicks last_or_missed_tick_time = time_source_->NextTickTime() - time_source_->Interval();
    if (current_begin_frame_args_.IsValid() && current_begin_frame_args_.frame_time == last_or_missed_tick_time && current_begin_frame_args_.interval == time_source_->Interval()) {
        // Ensure that the args have the right type.
        current_begin_frame_args_.type = BeginFrameArgs::MISSED;
    } else {
        // The args are not up to date and we need to create new ones with the
        // missed tick's time and a new sequence number.
        current_begin_frame_args_ = CreateBeginFrameArgs(last_or_missed_tick_time, BeginFrameArgs::MISSED);
    }

    BeginFrameArgs last_args = obs->LastUsedBeginFrameArgs();
    if (!last_args.IsValid() || (current_begin_frame_args_.frame_time > last_args.frame_time + current_begin_frame_args_.interval / kDoubleTickDivisor)) {
        obs->OnBeginFrame(current_begin_frame_args_);
    }
}

void DelayBasedBeginFrameSource::RemoveObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) != observers_.end());

    observers_.erase(obs);
    if (observers_.empty())
        time_source_->SetActive(false);
}

bool DelayBasedBeginFrameSource::IsThrottled() const
{
    return true;
}

void DelayBasedBeginFrameSource::OnTimerTick()
{
    current_begin_frame_args_ = CreateBeginFrameArgs(time_source_->LastTickTime(),
        BeginFrameArgs::NORMAL);
    std::unordered_set<BeginFrameObserver*> observers(observers_);
    for (auto* obs : observers) {
        BeginFrameArgs last_args = obs->LastUsedBeginFrameArgs();
        if (!last_args.IsValid() || (current_begin_frame_args_.frame_time > last_args.frame_time + current_begin_frame_args_.interval / kDoubleTickDivisor)) {
            obs->OnBeginFrame(current_begin_frame_args_);
        }
    }
}

// ExternalBeginFrameSource -----------------------------------------------
ExternalBeginFrameSource::ExternalBeginFrameSource(
    ExternalBeginFrameSourceClient* client)
    : client_(client)
{
    DCHECK(client_);
}

ExternalBeginFrameSource::~ExternalBeginFrameSource() = default;

void ExternalBeginFrameSource::AddObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) == observers_.end());

    bool observers_was_empty = observers_.empty();
    observers_.insert(obs);
    obs->OnBeginFrameSourcePausedChanged(paused_);
    if (observers_was_empty)
        client_->OnNeedsBeginFrames(true);

    // Send a MISSED begin frame if necessary.
    if (missed_begin_frame_args_.IsValid()) {
        BeginFrameArgs last_args = obs->LastUsedBeginFrameArgs();
        if (!last_args.IsValid() || (missed_begin_frame_args_.frame_time > last_args.frame_time)) {
            DCHECK((missed_begin_frame_args_.source_id != last_args.source_id) || (missed_begin_frame_args_.sequence_number > last_args.sequence_number));
            obs->OnBeginFrame(missed_begin_frame_args_);
        }
    }
}

void ExternalBeginFrameSource::RemoveObserver(BeginFrameObserver* obs)
{
    DCHECK(obs);
    DCHECK(observers_.find(obs) != observers_.end());

    observers_.erase(obs);
    if (observers_.empty()) {
        missed_begin_frame_args_ = BeginFrameArgs();
        client_->OnNeedsBeginFrames(false);
    }
}

bool ExternalBeginFrameSource::IsThrottled() const
{
    return true;
}

void ExternalBeginFrameSource::OnSetBeginFrameSourcePaused(bool paused)
{
    if (paused_ == paused)
        return;
    paused_ = paused;
    std::unordered_set<BeginFrameObserver*> observers(observers_);
    for (auto* obs : observers)
        obs->OnBeginFrameSourcePausedChanged(paused_);
}

void ExternalBeginFrameSource::OnBeginFrame(const BeginFrameArgs& args)
{
    missed_begin_frame_args_ = args;
    missed_begin_frame_args_.type = BeginFrameArgs::MISSED;
    std::unordered_set<BeginFrameObserver*> observers(observers_);
    for (auto* obs : observers)
        obs->OnBeginFrame(args);
}

} // namespace cc
